/*
 * detector.cpp
 * ------------
 * Reads a packet capture file and tries to identify
 * one machine trying to scan another machine based
 * on the ratio of requests-to-response. 
 *
 * We followed a tutorial on using libpcap from:
 * http://www.joshuarobinson.net/docs/libpcap_offline.html
 *
 * Built on a MAC, note that our tcp.h used tcphdr as the struct name.
 */

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <stdlib.h>
#include <cstdlib>

#include "pcap.h"
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include <map>
#include <set>

using namespace std;

#define ETHER_TYPE_IP (0x0800)
#define ETHER_TYPE_8021Q (0x8100)
#define THRESHOLD 3

struct Count_T{
  int requests; // Number of SYN packets from src
  int pos_responses; // Number of SYN/ACK packets from dst
  set<int> *pending_requests; // The set of ports that were sent SYN packets

  Count_T() {
    requests = 0;
    pos_responses = 0;
    pending_requests = new set<int>();
  }

  ~Count_T() {
    delete pending_requests;
  }
};

/* Prototypes */

void print_results(map<string, Count_T*> & counts);

/* Functions */

int main(int argc, const char* argv[]) {

  // Enforce number of arguments
  if (argc < 2) {
    cout << "Correct usage is \"" << argv[0] << " filename\"!" << endl;
    return -1;
  }

  struct pcap_pkthdr header;
  const u_char *packet;

  // Open the pcap file into handle.
  pcap_t *handle;
  char errbuf[PCAP_ERRBUF_SIZE];
  handle = pcap_open_offline(argv[1], errbuf);
  if(handle == NULL) {
    cout << "Couldn't open pcap file " << argv[1] << endl;
    return -1;
  }

  map<string, Count_T*> counts;

  // Scan through packets in order
  while(packet = pcap_next(handle, &header)) {
    // header contains information about the packet (e.g. timestamp) 
    u_char *pkt_ptr = (u_char *)packet; //cast a pointer to the packet data 

		//parse the first (ethernet) header, grabbing the type field 
    int ether_type = ((int)(pkt_ptr[12]) << 8) | (int)pkt_ptr[13]; 
    int ether_offset = 0; 

    if (ether_type == ETHER_TYPE_IP) //most common 
      ether_offset = 14; 
    else if (ether_type == ETHER_TYPE_8021Q) //my traces have this 
      ether_offset = 18; 
   
    // Parse the IP header 
    pkt_ptr += ether_offset;  //skip past the Ethernet II header 
    struct ip *ip_hdr = (struct ip *)pkt_ptr; //point to an IP header structure 

    int packet_length = ntohs(ip_hdr->ip_len);

    // Compute the source and destination IP addresses.
    struct in_addr ip_src = ip_hdr->ip_src;
    struct in_addr ip_dst = ip_hdr->ip_dst;

    char* ip_src_cstr = strdup(inet_ntoa(ip_src));
    char* ip_dst_cstr = strdup(inet_ntoa(ip_dst));

    string ip_src_str = ip_src_cstr;
    string ip_dst_str = ip_dst_cstr;

    // Compute if the SYN and ACK flags are set.
    struct tcphdr *tcp_hdr = (struct tcphdr *) (pkt_ptr + (ip_hdr->ip_hl *4));
    u_char th_flags = tcp_hdr->th_flags;

    // Process the packet based on type: SYN or SYN/ACK
    string key;
    int port;
    if(th_flags == TH_SYN) {
      key = ip_src_str + " " + ip_dst_str;
      port = tcp_hdr->th_dport;
    } else if (th_flags == (TH_SYN | TH_ACK)) {
      key = ip_dst_str + " " + ip_src_str;
      port = tcp_hdr->th_sport;
    } else {
      continue;
    }

    Count_T* value = counts[key];
    if(value == NULL) {
      value = new Count_T();
    }

    if(th_flags == TH_SYN) {
      value->pending_requests->insert(port);
      value->requests++;
    } else if (th_flags == (TH_SYN | TH_ACK)) {
      if(value->pending_requests->count(port)) {
        value->pos_responses++;
        value->pending_requests->erase(port);
      }
    }

    // Re-update the map.
    counts[key] = value;
  }

  pcap_close(handle);
  print_results(counts);
  return 0;
}

void print_results(map<string, Count_T*> & counts) {
  cout << "Source-IP Destination-IP" << endl;

  map<string, Count_T*>::const_iterator iter;
  for(iter=counts.begin(); iter != counts.end(); iter++) {
    // If the request to positive response ratio exceeds three, report it.
    if (iter->second->requests > THRESHOLD * iter->second->pos_responses)
      cout << iter->first << endl;
  }
}
